#!/usr/bin/env python3
"""
FFmpegAudioPlayer - 基于FFmpeg的音频播放器
提供统一的音频播放接口，具有最佳的Linux兼容性和音视频同步能力
"""

import subprocess
import time
import threading
import queue
import numpy as np
import os
from aiortc_custom.logger_config import log_info, log_debug, log_warning, log_error, log_success, log_failure, log_progress, log_connection, log_media, log_stats
from typing import Optional

class FFmpegAudioPlayer:
    """
    基于FFmpeg的音频播放器

    特性：
    - 使用FFmpeg进行音频播放
    - 自动音视频同步
    - 支持多种音频后端（ALSA/PulseAudio）
    - 跨平台兼容，特别优化Linux环境
    - 低延迟播放
    """

    def __init__(self, buffer_size: int = 20):
        self.buffer_size = buffer_size

        # 音频参数
        self.sample_rate = 48000
        self.channels = 1

        # FFmpeg进程相关
        self.ffmpeg_process: Optional[subprocess.Popen] = None
        self.is_running = False

        # 音频缓冲和同步
        self.audio_queue = queue.Queue(maxsize=5)  # 减小队列大小以降低延迟
        self.start_time: Optional[float] = None
        self.frame_count = 0
        self.dropped_frames = 0

        # 播放线程
        self.playback_thread: Optional[threading.Thread] = None
        self.stderr_thread: Optional[threading.Thread] = None

    def start(self, sample_rate: int = 48000, channels: int = 1) -> bool:
        """
        启动音频播放器

        Args:
            sample_rate: 采样率
            channels: 输入声道数

        Returns:
            bool: 是否启动成功
        """
        try:
            self.sample_rate = sample_rate
            # 使用原始声道数，不强制转换
            self.channels = max(1, int(channels))

            log_info(f"启动FFmpeg音频播放器: {sample_rate}Hz, {self.channels}声道")

            # 修复后的FFmpeg命令（参考LinuxAudioManager原始实现）
            cmd = [
                'ffplay',
                '-autoexit',
                '-nodisp',
                '-loglevel', 'error',
                '-f', 's16le',
                '-ar', str(sample_rate),
                '-ac', str(self.channels),
                '-acodec', 'pcm_s16le',
                '-'
            ]

            # 启动FFmpeg进程
            self.ffmpeg_process = subprocess.Popen(
                cmd,
                stdin=subprocess.PIPE,
                stdout=subprocess.DEVNULL,
                stderr=subprocess.PIPE,
                bufsize=0
            )

            # 等待一小段时间检查进程是否启动成功
            time.sleep(0.2)

            if self.ffmpeg_process.poll() is not None:
                # 进程已退出，获取错误信息
                stderr_output = self.ffmpeg_process.stderr.read().decode('utf-8', errors='ignore')
                log_failure(f"FFmpeg进程启动失败: {stderr_output}")
                return False

            self.is_running = True
            self.start_time = time.time()

            # 启动播放线程
            self.playback_thread = threading.Thread(target=self._playback_loop, daemon=True)
            self.playback_thread.start()

            log_success("FFmpeg音频播放器启动成功")
            return True

        except Exception as e:
            log_failure(f"FFmpeg音频播放器启动失败: {e}")
            self.is_running = False
            return False

    def write_frame(self, audio_data: np.ndarray, timestamp: Optional[float] = None) -> None:
        """
        写入音频数据

        Args:
            audio_data: 音频数据 (int16格式的numpy数组)
            timestamp: 可选的时间戳，用于同步
        """
        if not self.is_running or self.ffmpeg_process is None:
            return

        try:
            # 确保音频数据格式正确
            if audio_data is None or audio_data.size == 0:
                return

            # 转换为正确的格式
            if audio_data.dtype != np.int16:
                audio_data = self._convert_to_int16(audio_data)

            # 确保内存布局连续
            if not audio_data.flags['C_CONTIGUOUS']:
                audio_data = np.ascontiguousarray(audio_data, dtype=np.int16)

            # 将音频数据放入队列
            try:
                self.audio_queue.put_nowait(audio_data.copy())
            except queue.Full:
                # 队列满了，丢弃旧帧以避免延迟
                self.dropped_frames += 1
                try:
                    self.audio_queue.get_nowait()
                    self.audio_queue.put_nowait(audio_data.copy())
                except queue.Empty:
                    pass

            self.frame_count += 1

        except Exception as e:
            log_warning(f"写入音频帧失败: {e}")

    def _convert_to_int16(self, audio_data: np.ndarray) -> np.ndarray:
        """将音频数据转换为int16格式"""
        if audio_data.dtype == np.int16:
            return audio_data
        elif audio_data.dtype in [np.float32, np.float64]:
            # 浮点数转int16
            return (audio_data * 32767).clip(-32768, 32767).astype(np.int16)
        elif audio_data.dtype in [np.int8, np.uint8]:
            # 8位转16位
            return (audio_data.astype(np.int16) * 256).clip(-32768, 32767)
        elif audio_data.dtype in [np.int32, np.int64]:
            # 高精度转16位
            return (audio_data / (2**(audio_data.dtype.itemsize * 8 - 16))).astype(np.int16)
        else:
            return audio_data.astype(np.int16)

    def _playback_loop(self) -> None:
        """播放循环线程"""
        consecutive_errors = 0
        max_consecutive_errors = 5

        while self.is_running:
            try:
                audio_data = self.audio_queue.get(timeout=0.1)

                if self.ffmpeg_process and self.ffmpeg_process.stdin and not self.ffmpeg_process.stdin.closed:
                    # 写入音频数据到FFmpeg
                    audio_bytes = audio_data.tobytes()
                    self.ffmpeg_process.stdin.write(audio_bytes)
                    self.ffmpeg_process.stdin.flush()
                    consecutive_errors = 0  # 重置错误计数

            except queue.Empty:
                continue
            except (BrokenPipeError, ConnectionError) as e:
                consecutive_errors += 1
                log_warning(f"FFmpeg音频管道断开: {e}")
                if consecutive_errors >= max_consecutive_errors:
                    log_error("连续管道断开错误，停止音频播放")
                    self.is_running = False
                    break
                # 短暂等待后继续
                time.sleep(0.1)
            except Exception as e:
                consecutive_errors += 1
                log_warning(f"音频播放循环错误: {e}")
                if self.ffmpeg_process and self.ffmpeg_process.poll() is not None:
                    log_error("FFmpeg音频进程已终止")
                    break
                if consecutive_errors >= max_consecutive_errors:
                    log_error("连续播放错误，停止音频播放")
                    self.is_running = False
                    break
                time.sleep(0.1)

    def stop(self) -> None:
        """停止音频播放器"""
        log_info("正在停止FFmpeg音频播放器...")

        self.is_running = False

        # 停止播放线程
        if self.playback_thread and self.playback_thread.is_alive():
            self.playback_thread.join(timeout=1.0)

        # 终止FFmpeg进程
        if self.ffmpeg_process:
            try:
                if self.ffmpeg_process.stdin:
                    self.ffmpeg_process.stdin.close()
                self.ffmpeg_process.terminate()
                self.ffmpeg_process.wait(timeout=2.0)
            except Exception:
                try:
                    self.ffmpeg_process.kill()
                except Exception:
                    pass
            finally:
                self.ffmpeg_process = None

        # 清空队列
        while not self.audio_queue.empty():
            try:
                self.audio_queue.get_nowait()
            except queue.Empty:
                break

        log_success("FFmpeg音频播放器已停止")

    def get_stats(self) -> dict:
        """获取播放统计信息"""
        return {
            'is_running': self.is_running,
            'frame_count': self.frame_count,
            'dropped_frames': self.dropped_frames,
            'sample_rate': self.sample_rate,
            'channels': self.channels,
            'queue_size': self.audio_queue.qsize(),
            'running_time': time.time() - self.start_time if self.start_time else 0,
            'drop_rate': self.dropped_frames / max(1, self.frame_count) * 100
        }

    def __enter__(self):
        """上下文管理器支持"""
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        """上下文管理器支持"""
        self.stop()
